#include "formalbummixer.h"
#include "ui_formalbummixer.h"

#include <QFileDialog>
#include <QListWidgetItem>
#include <QDirIterator>
#include <QStringList>
#include <QTime>


FormAlbumMixer::FormAlbumMixer(CProfile *pProfile, QWidget *parent) :
    QWidget(parent), m_pProfile(pProfile),
    m_ui(new Ui::FormAlbumMixer)
{
    m_ui->setupUi(this);

    // connect signals/slots
    connect(&m_threadCache, SIGNAL(cacheAdded(QString , QListWidgetItem *)), SLOT(cacheAdded(QString , QListWidgetItem *)));
    connect(&m_threadCache, SIGNAL(cacheRemoved(QString , QListWidgetItem *)), SLOT(cacheRemoved(QString , QListWidgetItem *)));

    m_bIsModified = false;
    m_bAbortProcessing = false;

    // release one
    m_semaphoreSynch.release();

    doReshape();
}

FormAlbumMixer::~FormAlbumMixer()
{
    delete m_ui;
}

void FormAlbumMixer::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    switch (e->type()) {
    case QEvent::LanguageChange:
        m_ui->retranslateUi(this);
        break;
    default:
        break;
    }
}

void FormAlbumMixer::on_pushButtonSrcDir_clicked()
{
    QString strDir = QFileDialog::getExistingDirectory(this, tr("Open Source Directory"),
                      "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);

    if(!strDir.isEmpty())
    {
        m_ui->lineEditSrcDirectory->setText(strDir);
        m_ui->pushButtonPreview->setEnabled(true);
    }
}

void FormAlbumMixer::on_pushButtonDstDir_clicked()
{
    QString strDir = QFileDialog::getExistingDirectory(this, tr("Open Destination Directory"),
                      "", QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);

    if(!strDir.isEmpty())
    {
        m_ui->lineEditOutDirectory->setText(strDir);
        m_ui->pushButtonProcess->setEnabled(true);
    }
}

void FormAlbumMixer::on_listWidgetFiles_currentRowChanged(int currentRow)
{
    int nHeigh = m_ui->labelPictureOutput->minimumHeight();
    int nWidth = m_ui->labelPictureOutput->minimumWidth();
    QListWidgetItem * pItem = m_ui->listWidgetFiles->item(currentRow);
    if(pItem)
    {
        Log(QString("Scalling %1 to %2x%3").arg(pItem->text()).arg(nWidth).arg(nHeigh));
        QPixmap pixmapPicture = m_threadCache.GetPixMap(pItem->text(), pItem);
        m_ui->labelPictureOutput->setPixmap(pixmapPicture.scaled(nWidth, nHeigh, Qt::KeepAspectRatio, Qt::SmoothTransformation));
        // cache files nCacheSize around current row
        m_threadCache.CacheFiles(GetPix2Cache());
    }
    m_ui->progressBar->setValue(currentRow+1);
    m_ui->progressBar->update();

    // release sema
    if(!m_semaphoreSynch.available())
        m_semaphoreSynch.release();
}

void FormAlbumMixer::on_pushButtonPreview_clicked()
{
    m_bIsModified = true;
    // clear data
    m_ui->listWidgetFiles->clear();
    m_ui->listWidgetLog->clear();
    m_strlistFilesCache.clear();
    m_ui->progressBar->setValue(0);

    QStringList strlstExcludeDirs;

    if(!m_pProfile->m_strExcludeDirs.isEmpty())
        strlstExcludeDirs << m_pProfile->m_strExcludeDirs.split(",");

    QStringList strlistSelectedDirs;
    QString strSrcDir(m_ui->lineEditSrcDirectory->text());
    if(strSrcDir.isEmpty())
    {
        strlistSelectedDirs << m_pProfile->m_strlstSelectedDirs;
    }
    else
        strlistSelectedDirs << strSrcDir;

    m_nFilesCacheSize = 0;
    foreach(strSrcDir, strlistSelectedDirs)
    {
        Log(QString("Genrating preview for %1 ...").arg(strSrcDir));
        QStringList nameFilters;
        nameFilters << "*.jpg";
        QDirIterator it(strSrcDir, nameFilters, QDir::AllEntries | QDir::Files | QDir::NoDotAndDotDot,
                        QDirIterator::Subdirectories);

        while (it.hasNext())
        {
            QString strNext = it.next();
            foreach(QString strExcludeDir, strlstExcludeDirs)
            {
                if(strNext.indexOf(strExcludeDir)>=0)
                {
                    strNext.clear();
                    break;
                }
            }

            if(!strNext.isEmpty())
                m_strlistFilesCache << strNext;
        }

        m_nFilesCacheSize += m_strlistFilesCache.count();
        m_ui->progressCache->setMaximum(m_nFilesCacheSize);
    }

    // initialize random seed
    srand ( QTime::currentTime().msec() /*time(NULL)*/ );

    // select files
    int nMaxEntryNumber = m_pProfile->m_nMaxFiles < m_strlistFilesCache.count() ? m_pProfile->m_nMaxFiles : m_strlistFilesCache.count();
    while(nMaxEntryNumber--)
    {
        QListWidgetItem *pItem = new QListWidgetItem(ExtractOneRandomFilename(), m_ui->listWidgetFiles, QListWidgetItem::Type);
        pItem->setIcon(QIcon(":/images/water_drop.png"));
    }

    m_ui->progressBar->setMaximum(m_ui->listWidgetFiles->count());
    m_ui->pushButtonDstDir->setEnabled(true);
    m_ui->progressBar->setValue(m_ui->progressBar->maximum());
    m_ui->listWidgetFiles->setCurrentRow(0);

    Log("Done");
}

void FormAlbumMixer::on_pushButtonProcess_clicked()
{
    m_bAbortProcessing = false;
    m_ui->pushButtonSrcDir->setEnabled(false);
    m_ui->pushButtonDstDir->setEnabled(false);
    m_ui->pushButtonPreview->setEnabled(false);
    m_ui->pushButtonProcess->setEnabled(false);

    int nHeigh = m_pProfile->m_nHeight;
    int nWidth = m_pProfile->m_nWidth;
    m_ui->progressBar->setMaximum(m_ui->listWidgetFiles->count());
    Log("Processing files ...");

    int nStartNumber = m_pProfile->m_nStartNumber;
    for(int iRow = 0; iRow < m_ui->listWidgetFiles->count(); iRow++)
    {
        if(m_pProfile->m_bEnablePreviewWhileProcessing)
            m_ui->listWidgetFiles->setCurrentRow(iRow);
        QListWidgetItem *pItem = m_ui->listWidgetFiles->item(iRow);
        QString strOutputName(QString("%1/pimi%2.jpg").arg(m_ui->lineEditOutDirectory->text()).arg(nStartNumber++, 4, 10, QChar('0')));
        QPixmap pixmapPicture(pItem->text());
        QPixmap(pixmapPicture.scaled(nWidth, nHeigh, Qt::KeepAspectRatio, Qt::SmoothTransformation)).save(strOutputName, "JPG", m_pProfile->m_nImageQuality);
        m_ui->progressBar->setValue(iRow+1);
        Log(QString("Processed %1 -> %2, scaled %3x%4, quality %5").arg(pItem->text()).arg(strOutputName).arg(nWidth).arg(nHeigh).arg(m_pProfile->m_nImageQuality));

        if(m_bAbortProcessing)
        {
            Log("Aborted!");
            break;
        }
    }

    m_ui->pushButtonSrcDir->setEnabled(true);
    m_ui->pushButtonDstDir->setEnabled(true);
    m_ui->pushButtonPreview->setEnabled(true);
    m_ui->pushButtonProcess->setEnabled(true);
}

void FormAlbumMixer::on_listWidgetFiles_doubleClicked(QModelIndex index)
{
    actionDelete();
}

void FormAlbumMixer::actionDelete()
{
    // acquire sema
    // sometimes the action (double delete) is executed when GUI is busy (performs a row change) and crash the application
    //Log(QString("TID AD %1").arg((int)thread()->currentThreadId()));
    if(!m_semaphoreSynch.tryAcquire())
    {
        Log("Another action is pending. Delete is canceled.");
        return;
    }

    m_bIsModified = true;

    QListWidgetItem * pCurrent = m_ui->listWidgetFiles->currentItem();
    if(pCurrent)
    {
        // remove from cache
        m_threadCache.RemoveFromCache(pCurrent->text());
        // add one at the end
        QString strNewFileName = ExtractOneRandomFilename();
        if(!strNewFileName.isEmpty())
        {
            QListWidgetItem *pItem = new QListWidgetItem(strNewFileName, m_ui->listWidgetFiles, QListWidgetItem::Type);
            pItem->setIcon(QIcon(":/images/water_drop.png"));
        }
        // delete
        pCurrent->setHidden(true);
        delete pCurrent;
        m_ui->progressBar->setMaximum(m_ui->listWidgetFiles->count());
    }
}

#define PIMI_DATAFILESIGNATURE "e8db14be-ce96-4691-9f21-4e4d763e9251"
bool FormAlbumMixer::actionSave()
{
    QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
                                "", tr("Pimi file(*.pimi)"));

    if(fileName.isEmpty())
    {
        Log("Save canceled");
        return false;
    }

    // open data file
    QFile file(fileName);
    if(!file.open(QIODevice::WriteOnly))
    {
        Log(QString("Unable to open %1 to save data").arg(fileName));
        return false;
    }
    // we will serialize the data into the file
    QDataStream out(&file);

    int nCurrentRow = m_ui->listWidgetFiles->currentRow();
    QStringList strlistFiles;

    for(int iRow = 0; iRow < m_ui->listWidgetFiles->count(); iRow++)
    {
        QListWidgetItem *pItem = m_ui->listWidgetFiles->item(iRow);
        strlistFiles << pItem->text();
    }

    out << QString(PIMI_DATAFILESIGNATURE);
    out << nCurrentRow;
    out << strlistFiles;
    out << m_nFilesCacheSize;
    out << m_strlistFilesCache;  // save cache

    m_bIsModified = false;

    Log(QString("Saved to %1").arg(fileName));

    return true;
}

void FormAlbumMixer::actionLoad()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"),
                                "", tr("Pimi file(*.pimi)"));

    if(fileName.isEmpty())
    {
        Log("Load canceled");
        return;
    }

    // open data file
    QFile file(fileName);
    if(!file.open(QIODevice::ReadOnly))
    {
        Log(QString("Unable to open %1 to save data").arg(fileName));
        return;
    }
    // we will serialize the data into the file
    QDataStream in(&file);

    QString strFileSignature;
    int nCurrentRow;
    QStringList strlistFiles;

    in >> strFileSignature;
    if(strFileSignature != PIMI_DATAFILESIGNATURE)
    {
        Log(QString("Data file %1 signature is not correct").arg(fileName));
        return;
    }
    // reset controls
    m_ui->pushButtonSrcDir->setEnabled(true);
    m_ui->pushButtonDstDir->setEnabled(true);
    m_ui->pushButtonPreview->setEnabled(false);
    m_ui->pushButtonProcess->setEnabled(false);

    in >> nCurrentRow;
    in >> strlistFiles;
    in >> m_nFilesCacheSize;
    in >> m_strlistFilesCache;

    m_ui->listWidgetFiles->clear();
    for(int iRow = 0; iRow < strlistFiles.count(); iRow++)
    {
        QListWidgetItem *pItem = new QListWidgetItem(strlistFiles.at(iRow), m_ui->listWidgetFiles, QListWidgetItem::Type);
        pItem->setIcon(QIcon(":/images/water_drop.png"));
    }

    m_ui->progressBar->setMaximum(strlistFiles.count());
    m_ui->listWidgetFiles->setCurrentRow(nCurrentRow);

    // restore cache progress bar
    m_ui->progressCache->setMaximum(m_nFilesCacheSize);
    m_ui->progressCache->setValue(m_strlistFilesCache.count());

    Log(QString("Loaded from %1").arg(fileName));
}

void FormAlbumMixer::actionAbortProcessing()
{
    m_bAbortProcessing = true;
}

void FormAlbumMixer::doReshape()
{
    m_ui->labelPictureOutput->setMinimumSize(QSize(m_pProfile->m_nWidthPreview, m_pProfile->m_nHeightPreview));
    m_ui->labelPictureOutput->setMaximumSize(QSize(m_pProfile->m_nWidthPreview, m_pProfile->m_nHeightPreview));
    m_ui->labelPictureOutput->update();

    // enable preview if selected dirs are defined
    if(m_pProfile->m_strlstSelectedDirs.count())
        m_ui->pushButtonPreview->setEnabled(true);
}

bool FormAlbumMixer::isModified() const
{
    return m_bIsModified;
}

QString FormAlbumMixer::ExtractOneRandomFilename()
{
    m_bIsModified = true;
    QString strFileName;
    int nCount = m_strlistFilesCache.count();
    if(nCount)
    {
        int iIdx = (int)(((double)rand()/RAND_MAX) * nCount);
        strFileName = m_strlistFilesCache.at(iIdx);
        m_strlistFilesCache.removeAt(iIdx);
    }

    nCount = m_strlistFilesCache.count();
    m_ui->progressCache->setValue(nCount);

    return strFileName;
}

void FormAlbumMixer::Log(QString strLogData)
{
    QListWidgetItem *pItem = new QListWidgetItem(strLogData, m_ui->listWidgetLog, QListWidgetItem::Type);
    m_ui->listWidgetLog->setCurrentItem(pItem);
    QCoreApplication::processEvents();
}

QList<CACHE_REQUEST> FormAlbumMixer::GetPix2Cache()
{
    int nCacheSize = m_pProfile->m_nCacheSize;
    int iCurrentRow = m_ui->listWidgetFiles->currentRow();
    int nCount = m_ui->listWidgetFiles->count();

    QList<CACHE_REQUEST>  listCacheRequestJob;
    QList<CACHE_REQUEST>  listCacheRequestJobUp;
    QList<CACHE_REQUEST>  listCacheRequestJobDown;

    QListWidgetItem *pItem = m_ui->listWidgetFiles->item(iCurrentRow);
    listCacheRequestJob << CACHE_REQUEST(pItem->text(), pItem);

    bool bUpDone = false;
    bool bDownDone = false;
    for(int iIdx=1; !bUpDone || !bDownDone; iIdx++)
    {
        int iRowUp = iCurrentRow + iIdx;
        int iRowDown = iCurrentRow - iIdx;

        if(iRowUp < nCount && listCacheRequestJobUp.count() < nCacheSize)
        {
            pItem = m_ui->listWidgetFiles->item(iRowUp);
            if(!pItem->isHidden())
                listCacheRequestJobUp << CACHE_REQUEST(pItem->text(), pItem);
        }
        else
            bUpDone = true;


        if(iRowDown >= 0 && listCacheRequestJobDown.count() < nCacheSize)
        {
            pItem = m_ui->listWidgetFiles->item(iRowDown);
            if(!pItem->isHidden())
                listCacheRequestJobDown << CACHE_REQUEST(pItem->text(), pItem);
        }
        else
            bDownDone = true;

    }

    listCacheRequestJob << listCacheRequestJobUp << listCacheRequestJobDown ;

    return listCacheRequestJob;
}

void FormAlbumMixer::cacheAdded(QString strFileName, QListWidgetItem *pItem)
{
    if(pItem)
        pItem->setIcon(QIcon(":/images/slime_drop.png"));

    Log(QString("Cache ADDED %1").arg(strFileName));
}

void FormAlbumMixer::cacheRemoved(QString strFileName, QListWidgetItem *pItem)
{
    if(pItem)
        pItem->setIcon(QIcon(":/images/water_drop.png"));

    Log(QString("Cache REMOVED %1").arg(strFileName));
}

// ------------------------------- CacheThread ------------------------------------
void CacheThread::CacheFiles(QList<CACHE_REQUEST> listPixCacheJob)
{
    m_mutexSynch.lock();
    m_listPixCacheJob.clear();
    m_listPixCacheJob << listPixCacheJob;
    m_mutexSynch.unlock();
    // start task in back ground
    start(IdlePriority);
}

void CacheThread::run()
{
    do
    {
        // move the job list
        m_mutexSynch.lock();
        QList<CACHE_REQUEST>  listPixCacheJob(m_listPixCacheJob);
        m_listPixCacheJob.clear();
        m_mutexSynch.unlock();

        // clean the map from obsolete elements
         QMapIterator<QString,CACHE_DATA> i(m_mapCache);
         while (i.hasNext())
         {
            i.next();
            QString strPixFileName = i.key();
            bool bFound = false;
            foreach(CACHE_REQUEST cacheRequest, listPixCacheJob)
            {
                if(cacheRequest.m_strFileName == strPixFileName)
                {
                    bFound = true;
                    break;
                }
            }
            if(!bFound)
            {
                emit cacheRemoved(strPixFileName, m_mapCache[strPixFileName].m_pListWidgetItem);
                m_mutexSynch.lock();
                m_mapCache.remove(strPixFileName);
                m_mutexSynch.unlock();
            }
         }

        // process the list
        foreach(CACHE_REQUEST cacheRequest, listPixCacheJob)
        {
            GetPixMap(cacheRequest.m_strFileName, cacheRequest.m_pListWidgetItem);
        }
    }  while (m_listPixCacheJob.count());
}

QPixmap CacheThread::GetPixMap(QString strFileName, QListWidgetItem *pListWidgetItem)
{
    m_mutexSynch.lock();
    if(!m_mapCache.contains(strFileName))
    {
        m_mapCache[strFileName] = CACHE_DATA(strFileName, pListWidgetItem);
        emit cacheAdded(strFileName, pListWidgetItem);
    }
    QPixmap pixMap = QPixmap::fromImage(m_mapCache[strFileName].m_Img);
    m_mutexSynch.unlock();

    return pixMap;
}

void CacheThread::RemoveFromCache(QString strFileName)
{
    m_mutexSynch.lock();
    m_mapCache.remove(strFileName);
    m_mutexSynch.unlock();
    emit cacheRemoved(strFileName, NULL);
}
